/*
 * (C) 2003-2006 Gabest
 * (C) 2006-2014 see Authors.txt
 *
 * This file is part of MPC-HC.
 *
 * MPC-HC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * MPC-HC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "stdafx.h"
#include "DX7SubPic.h"
#include <ddraw.h>

//
// CDX7SubPic
//

CDX7SubPic::CDX7SubPic(IDirect3DDevice7* pD3DDev, IDirectDrawSurface7* pSurface)
    : m_pD3DDev(pD3DDev)
    , m_pSurface(pSurface)
{
    DDSURFACEDESC2 ddsd;
    INITDDSTRUCT(ddsd);
    if (SUCCEEDED(m_pSurface->GetSurfaceDesc(&ddsd))) {
        m_maxsize.SetSize(ddsd.dwWidth, ddsd.dwHeight);
        m_rcDirty.SetRect(0, 0, ddsd.dwWidth, ddsd.dwHeight);
    }
}

// ISubPic

STDMETHODIMP_(void*) CDX7SubPic::GetObject()
{
    return (IDirectDrawSurface7*)m_pSurface;
}

STDMETHODIMP CDX7SubPic::GetDesc(SubPicDesc& spd)
{
    DDSURFACEDESC2 ddsd;
    INITDDSTRUCT(ddsd);
    if (FAILED(m_pSurface->GetSurfaceDesc(&ddsd))) {
        return E_FAIL;
    }

    spd.type = 0;
    spd.w = m_size.cx;
    spd.h = m_size.cy;
    spd.bpp = (WORD)ddsd.ddpfPixelFormat.dwRGBBitCount;
    spd.pitch = ddsd.lPitch;
    spd.bits = (BYTE*)ddsd.lpSurface; // should be NULL
    spd.vidrect = m_vidrect;

    return S_OK;
}

STDMETHODIMP CDX7SubPic::CopyTo(ISubPic* pSubPic)
{
    HRESULT hr;
    if (FAILED(hr = __super::CopyTo(pSubPic))) {
        return hr;
    }

    CPoint p = m_rcDirty.TopLeft();
    hr = m_pD3DDev->Load((IDirectDrawSurface7*)pSubPic->GetObject(), &p, m_pSurface, m_rcDirty, 0);

    return SUCCEEDED(hr) ? S_OK : E_FAIL;
}

STDMETHODIMP CDX7SubPic::ClearDirtyRect(DWORD color)
{
    if (m_rcDirty.IsRectEmpty()) {
        return S_FALSE;
    }

    DDBLTFX fx;
    INITDDSTRUCT(fx);
    fx.dwFillColor = color;
    m_pSurface->Blt(&m_rcDirty, nullptr, nullptr, DDBLT_WAIT | DDBLT_COLORFILL, &fx);

    m_rcDirty.SetRectEmpty();

    return S_OK;
}

STDMETHODIMP CDX7SubPic::Lock(SubPicDesc& spd)
{
    DDSURFACEDESC2 ddsd;
    INITDDSTRUCT(ddsd);
    if (FAILED(m_pSurface->Lock(nullptr, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, nullptr))) {
        return E_FAIL;
    }

    spd.type = 0;
    spd.w = m_size.cx;
    spd.h = m_size.cy;
    spd.bpp = (WORD)ddsd.ddpfPixelFormat.dwRGBBitCount;
    spd.pitch = ddsd.lPitch;
    spd.bits = (BYTE*)ddsd.lpSurface;
    spd.vidrect = m_vidrect;

    return S_OK;
}

STDMETHODIMP CDX7SubPic::Unlock(RECT* pDirtyRect)
{
    m_pSurface->Unlock(nullptr);

    if (pDirtyRect) {
        m_rcDirty = *pDirtyRect;
        m_rcDirty.InflateRect(1, 1);
        m_rcDirty &= CRect(CPoint(0, 0), m_size);
    } else {
        m_rcDirty = CRect(CPoint(0, 0), m_size);
    }

    return S_OK;
}

STDMETHODIMP CDX7SubPic::AlphaBlt(RECT* pSrc, RECT* pDst, SubPicDesc* pTarget)
{
    ASSERT(pTarget == nullptr);

    if (!m_pD3DDev || !m_pSurface || !pSrc || !pDst) {
        return E_POINTER;
    }

    CRect src(*pSrc), dst(*pDst);

    DDSURFACEDESC2 ddsd;
    INITDDSTRUCT(ddsd);
    if (FAILED(m_pSurface->GetSurfaceDesc(&ddsd))) {
        return E_FAIL;
    }

    float w = (float)ddsd.dwWidth;
    float h = (float)ddsd.dwHeight;

    // Be careful with the code that follows. Some compilers (e.g. Visual Studio 2012) used to miscompile
    // it in some cases (namely x64 with optimizations /O2 /Ot). This bug led pVertices not to be correctly
    // initialized and thus the subtitles weren't shown.
    struct {
        float x, y, z, rhw;
        float tu, tv;
    } pVertices[] = {
        {(float)dst.left, (float)dst.top, 0.5f, 2.0f, (float)src.left / w, (float)src.top / h},
        {(float)dst.right, (float)dst.top, 0.5f, 2.0f, (float)src.right / w, (float)src.top / h},
        {(float)dst.left, (float)dst.bottom, 0.5f, 2.0f, (float)src.left / w, (float)src.bottom / h},
        {(float)dst.right, (float)dst.bottom, 0.5f, 2.0f, (float)src.right / w, (float)src.bottom / h},
    };

    for (size_t i = 0; i < _countof(pVertices); i++) {
        pVertices[i].x -= 0.5f;
        pVertices[i].y -= 0.5f;
    }

    m_pD3DDev->SetTexture(0, m_pSurface);

    m_pD3DDev->SetRenderState(D3DRENDERSTATE_CULLMODE, D3DCULL_NONE);
    m_pD3DDev->SetRenderState(D3DRENDERSTATE_LIGHTING, FALSE);
    m_pD3DDev->SetRenderState(D3DRENDERSTATE_BLENDENABLE, TRUE);
    m_pD3DDev->SetRenderState(D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE); // pre-multiplied src and ...
    m_pD3DDev->SetRenderState(D3DRENDERSTATE_DESTBLEND, m_bInvAlpha ? D3DBLEND_INVSRCALPHA : D3DBLEND_SRCALPHA); // ... inverse alpha channel for dst

    m_pD3DDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
    m_pD3DDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    m_pD3DDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

    if (src == dst) {
        m_pD3DDev->SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTFG_POINT);
        m_pD3DDev->SetTextureStageState(0, D3DTSS_MINFILTER, D3DTFG_POINT);
    } else {
        m_pD3DDev->SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTFG_LINEAR);
        m_pD3DDev->SetTextureStageState(0, D3DTSS_MINFILTER, D3DTFG_LINEAR);
    }
    m_pD3DDev->SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTFP_NONE);

    m_pD3DDev->SetTextureStageState(0, D3DTSS_ADDRESS, D3DTADDRESS_CLAMP);

    /*//
    D3DDEVICEDESC7 d3ddevdesc;
    m_pD3DDev->GetCaps(&d3ddevdesc);
    if (d3ddevdesc.dpcTriCaps.dwAlphaCmpCaps & D3DPCMPCAPS_LESS)
    {
        m_pD3DDev->SetRenderState(D3DRENDERSTATE_ALPHAREF, (DWORD)0x000000FE);
        m_pD3DDev->SetRenderState(D3DRENDERSTATE_ALPHATESTENABLE, TRUE);
        m_pD3DDev->SetRenderState(D3DRENDERSTATE_ALPHAFUNC, D3DPCMPCAPS_LESS);
    }
    *///

    if (FAILED(m_pD3DDev->BeginScene())) {
        return E_FAIL;
    }

    m_pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP,
                             D3DFVF_XYZRHW | D3DFVF_TEX1,
                             pVertices, 4, D3DDP_WAIT);
    m_pD3DDev->EndScene();

    m_pD3DDev->SetTexture(0, nullptr);

    return S_OK;
}

//
// CDX7SubPicAllocator
//

CDX7SubPicAllocator::CDX7SubPicAllocator(IDirect3DDevice7* pD3DDev, SIZE maxsize)
    : CSubPicAllocatorImpl(maxsize, true)
    , m_pD3DDev(pD3DDev)
    , m_maxsize(maxsize)
{
}

// ISubPicAllocator

STDMETHODIMP CDX7SubPicAllocator::ChangeDevice(IUnknown* pDev)
{
    CComQIPtr<IDirect3DDevice7, &IID_IDirect3DDevice7> pD3DDev = pDev;
    CheckPointer(pD3DDev, E_NOINTERFACE);

    CAutoLock cAutoLock(this);
    HRESULT hr = S_FALSE;
    if (m_pD3DDev != pD3DDev) {
        m_pD3DDev = pD3DDev;
        hr = __super::ChangeDevice(pDev);
    }

    return hr;
}

STDMETHODIMP CDX7SubPicAllocator::SetMaxTextureSize(SIZE maxTextureSize)
{
    CAutoLock cAutoLock(this);
    if (m_maxsize != maxTextureSize) {
        m_maxsize = maxTextureSize;
    }

    return SetCurSize(m_maxsize);
}

// ISubPicAllocatorImpl

bool CDX7SubPicAllocator::Alloc(bool fStatic, ISubPic** ppSubPic)
{
    if (!ppSubPic) {
        return false;
    }

    if (m_maxsize.cx <= 0 || m_maxsize.cy <= 0) {
        return false;
    }

    CAutoLock cAutoLock(this);

    DDSURFACEDESC2 ddsd;
    INITDDSTRUCT(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | (fStatic ? DDSCAPS_SYSTEMMEMORY : 0);
    ddsd.ddsCaps.dwCaps2 = fStatic ? 0 : (DDSCAPS2_TEXTUREMANAGE | DDSCAPS2_HINTSTATIC);
    ddsd.dwWidth = m_maxsize.cx;
    ddsd.dwHeight = m_maxsize.cy;
    ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
    ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
    ddsd.ddpfPixelFormat.dwRGBBitCount = 32;
    ddsd.ddpfPixelFormat.dwRGBAlphaBitMask = 0xFF000000;
    ddsd.ddpfPixelFormat.dwRBitMask        = 0x00FF0000;
    ddsd.ddpfPixelFormat.dwGBitMask        = 0x0000FF00;
    ddsd.ddpfPixelFormat.dwBBitMask        = 0x000000FF;

    CComPtr<IDirect3D7> pD3D;
    CComQIPtr<IDirectDraw7, &IID_IDirectDraw7> pDD;
    if (FAILED(m_pD3DDev->GetDirect3D(&pD3D)) || !pD3D || !(pDD = pD3D)) {
        return false;
    }

    CComPtr<IDirectDrawSurface7> pSurface;
    if (FAILED(pDD->CreateSurface(&ddsd, &pSurface, nullptr))) {
        return false;
    }

    try {
        *ppSubPic = DEBUG_NEW CDX7SubPic(m_pD3DDev, pSurface);
    } catch (CMemoryException* e) {
        e->Delete();
        return false;
    }

    (*ppSubPic)->AddRef();

    return true;
}
